package lumis.search.lucene;

import java.util.Iterator;
import java.util.List;

import lumis.search.filter.ISearchQueryFilter;
import lumis.search.filter.SearchClause;
import lumis.search.filter.SearchFilterGroup;

/**
 * Parses {@link ISearchQueryFilter} to Lucene query language. 
 *
 * @version $Revision: 10458 $ $Date: 2009-06-02 15:49:09 -0300 (Tue, 02 Jun 2009) $
 * @since 4.2.2
 */
public class LuceneSearchQueryFilterParser
{
	/**
	 * A parser to {@link ISearchQueryFilter}.
	 * 
	 * @param queryFilter to be parsed.
	 * @return the string query.
	 * @since 4.2.2
	 */
	public String parse(ISearchQueryFilter queryFilter)
	{
		// Parse SearchFilterGroup
		if(queryFilter instanceof SearchFilterGroup)
		{
			return parseFilterGroup((SearchFilterGroup)queryFilter);
		}
		// Parse SearchClause
		else if(queryFilter instanceof SearchClause)
		{
			return parseClause((SearchClause)queryFilter);
		}
		else
			throw new IllegalArgumentException("Invalid queryFilter: "+queryFilter);
		
	}
	
	/**
	 * A parser to {@link SearchFilterGroup#getOperator()}. 
	 * If the operator is not supported a {@link IllegalArgumentException} is given.
	 * 
	 * @param operator to be parsed.
	 * @return the group operator.
	 * @since 4.2.2
	 */
	protected String parseGroupOperator(String operator)
	{
		if(SearchFilterGroup.GROUP_OPERATOR_OR.equals(operator))
			return "OR";
		else if(SearchFilterGroup.GROUP_OPERATOR_AND.equals(operator))
			return "AND";
		else
			throw new IllegalArgumentException("Invalid operator: "+operator);
	}
	
	/**
	 * A parser to {@link SearchClause#getOperator()}.
	 * If the operator is not supported a {@link IllegalArgumentException} is given.
	 * 
	 * @param operator
	 * @return the clause operator.
	 * @since 4.2.2
	 */
	protected void verifyClauseOperator(String operator)
	{
		if(!SearchClause.CLAUSE_OPERATOR_ANY.equals(operator))
			throw new IllegalArgumentException("Invalid operator: "+operator);
	}
	
	/**
	 * A parser to {@link SearchFilterGroup} for Lucene.
	 * @param searchFilterGroup to be parsed.
	 * @return the filterGroup or the clause.
	 * @since 4.2.2
	 */
	protected String parseFilterGroup(SearchFilterGroup searchFilterGroup)
	{
		// get a parsed operator.
		String operator = parseGroupOperator(searchFilterGroup.getOperator());
		List<? extends ISearchQueryFilter> filters = searchFilterGroup.getFilters();
		
		String groupString = "";
		// if the list of filters is empty. returns a empty string.
		if(filters.isEmpty())
		{
			return groupString;
		}
		else
		{
			StringBuilder groupBuilder = new StringBuilder();
			boolean hasOperator = false;
			// 
			for(Iterator<? extends ISearchQueryFilter> i = filters.iterator(); i.hasNext();)
			{
				String clause = parse(i.next());
				if(clause != null && clause.length() > 0)
				{
					if(groupBuilder != null && groupBuilder.length() > 0)
					{
						groupBuilder.append(" "+operator+" ");
						hasOperator = true;
					}
					groupBuilder.append(clause);
				}
			}
			// Involve the clause with parenthesis if it has more than one element.
			if(groupBuilder != null && groupBuilder.length() > 0)
				groupString = groupBuilder.toString();
			if(hasOperator)
				groupString = "("+groupString+")";
			
			return groupString;
		}
	}
	
	/**
	 * A parser to {@link SearchClause} for Lucene. Usually return a string clause like
	 * <code>
	 * scope:("term1" "term2" ...)
	 * </code>
	 * @param searchClause to be parsed.
	 * @return the string clause.
	 * @since 4.2.2
	 */
	protected String parseClause(SearchClause searchClause)
	{
		// verifies the operator.
		String operator = searchClause.getOperator();
		verifyClauseOperator(operator);
		
		List<String> terms = searchClause.getTerms();
		// if the list of terms is empty returns a empty string.
		if(terms.isEmpty())
		{
			return "";
		}
		else
		{
			// choose the syntax according to the operator.
			StringBuilder clauseBuilder = new StringBuilder();
			if(SearchClause.CLAUSE_OPERATOR_ANY.equals(operator))
			{
				String scope = searchClause.getScope();
				// puts the scope if it exists
				if(scope != null)
					clauseBuilder.append(scope).append(":");
				// puts all terms
				clauseBuilder.append("(");
				for (Iterator<String> i = terms.iterator(); i.hasNext();)
				{
					String term = i.next();
					term = escapeClauseTerm(term);
					clauseBuilder.append("\""+term+"\"");
					if(i.hasNext())
						clauseBuilder.append(" ");
					else
						clauseBuilder.append(")");
				}
			}
			return clauseBuilder.toString();
		}
	}

	/**
	 * Escapes the clause term.
	 * 
	 * @param the term to be escaped.
	 * @return escaped term.
	 * @since 4.2.2
	 */
	protected String escapeClauseTerm(String term)
	{
		term = term.replace('"', ' ');
		return term;
	}
}
